﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;

using System.CodeDom;
using System.CodeDom.Compiler;
using System.Net;
using System.Reflection;
using System.Web.Services.Description;
using System.Web.Services.Discovery;
using System.Xml.Schema;
using Microsoft.CSharp;
using System.Text.RegularExpressions;
using System.Web.Services.Protocols;
using System.Threading.Tasks;
using System.Dynamic;

using Esb.Domain.Helpers;

namespace Esb.Soa.Domain.ServiceHandlers
{
    /// <summary>
    /// Web Service 代理类
    /// </summary>
    public class WebServiceHandler : IServiceHandler
    {
        #region Ctors

        /// <summary>
        /// 
        /// </summary>
        static WebServiceHandler()
        {
            wsInfos = new List<WSInfo>();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="esbUrl"></param>
        public WebServiceHandler()
        {
            
        }

        #endregion

        #region Fields

        /// <summary>
        /// Web服务信息集合
        /// </summary>
        internal static List<WSInfo> wsInfos;

        /// <summary>
        /// Esb地址
        /// </summary>
        string esbUrl;

        /// <summary>
        /// Wsdl地址
        /// </summary>
        string wsdlUrl;

        /// <summary>
        /// 方法名
        /// </summary>
        //string methodName;

        /// <summary>
        /// 动态编译类命名空间
        /// </summary>
        string @namespace;

        #endregion

        #region Methods

        /// <summary>
        /// 请求
        /// </summary>
        /// <param name="requestBody"></param>
        /// <param name="serviceUrl"></param>
        /// <param name="methodName"></param>
        /// <returns></returns>
        public string ProcessCallService(string requestBody, string serviceUrl, string methodName)
        {
            this.esbUrl = serviceUrl;
            this.wsdlUrl = serviceUrl + "?wsdl";

            string result = string.Empty;

            @namespace = "Framework.Zhaogang.Esb.Soa.Domain.ServiceHandlers" + methodName;
            string url = serviceUrl + "/" + methodName;

            WSInfo service = wsInfos.SingleOrDefault(d => d.Url.Equals(url, StringComparison.CurrentCultureIgnoreCase));

            if (service != null && service.Methods != null)
            {
                WSMethod method = service.Methods.SingleOrDefault(m => m.MethodName.Equals(methodName, StringComparison.CurrentCultureIgnoreCase));
                if (method != null)
                {
                    result = SerializeHelper.Serialize(method.Method.Invoke(method.Class, new object[] { SerializeHelper.Deserialize(requestBody, method.DynamicType) }));
                }
                else
                {
                    result = DynamicCalling(service.Assembly, url, methodName, requestBody, service.Class);
                    //使用之前DynamicCompile()动态编译过的结果
                }
            }
            else
            {
                result = DynamicCalling(DynamicCompile(), url, methodName, requestBody, null);
            }

            return result;
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// 动态编译远程程序集
        /// </summary>
        /// <returns></returns>
        private Assembly DynamicCompile()
        {
            try
            {
                var assembly = default(Assembly);
                using (var webClient = new WebClient())
                {
                    using (var stream = webClient.OpenRead(wsdlUrl))
                    {
                        var serviceDescription = ServiceDescription.Read(stream);
                        var serviceDescriptionImporter = new ServiceDescriptionImporter();
                        serviceDescriptionImporter.ProtocolName = "Soap";
                        serviceDescriptionImporter.Style = ServiceDescriptionImportStyle.Client;
                        serviceDescriptionImporter.CodeGenerationOptions = System.Xml.Serialization.CodeGenerationOptions.GenerateProperties | System.Xml.Serialization.CodeGenerationOptions.GenerateNewAsync;
                        serviceDescriptionImporter.AddServiceDescription(serviceDescription, "", "");
                        var codeNamespace = new CodeNamespace(@namespace);

                        var codeCompileUnit = new CodeCompileUnit();
                        codeCompileUnit.Namespaces.Add(codeNamespace);
                        CheckServiceDescriptionImporterImports(serviceDescriptionImporter);
                        serviceDescriptionImporter.Import(codeNamespace, codeCompileUnit);
                        var cSharpCodeProvider = new CSharpCodeProvider();

                        var compilerParameters = new CompilerParameters();
                        compilerParameters.GenerateExecutable = false;
                        compilerParameters.GenerateInMemory = true;
                        compilerParameters.ReferencedAssemblies.Add("System.dll");
                        compilerParameters.ReferencedAssemblies.Add("System.XML.dll");
                        compilerParameters.ReferencedAssemblies.Add("System.Web.Services.dll");
                        compilerParameters.ReferencedAssemblies.Add("System.Data.dll");

                        var compilerResults = cSharpCodeProvider.CompileAssemblyFromDom(compilerParameters, codeCompileUnit);
                        if (compilerResults.Errors.HasErrors == true)
                        {
                            var stringBuilder = new StringBuilder();
                            foreach (var compilerError in compilerResults.Errors)
                            {
                                stringBuilder.Append(compilerError.ToString());
                                stringBuilder.Append(Environment.NewLine);
                            }
                            throw new Exception(stringBuilder.ToString());
                        }

                        assembly = compilerResults.CompiledAssembly;
                        stream.Close();
                        stream.Dispose();
                    }
                    webClient.Dispose();
                }
                return assembly;
            }
            catch (Exception exception)
            {
                throw new Exception(exception.InnerException.Message, new Exception(exception.InnerException.StackTrace));
            }
        }

        /// <summary>
        /// 处理方法
        /// </summary>
        /// <param name="assembly"></param>
        /// <param name="url"></param>
        /// <param name="_methodName"></param>
        /// <param name="service"></param>
        /// <returns></returns>
        private WSMethod ProcessMethod(Assembly assembly, string url, string _methodName, object service)
        {
            var serviceTypes = assembly.GetTypes().Where(t => t.BaseType == typeof(SoapHttpClientProtocol));
            var serviceType = serviceTypes.First();
            if (service == null)
            {
                service = Activator.CreateInstance(serviceType);
            }
            //之前创建了过多的服务实例，每方法都创建，反射性能问题，现在复用服务实例
            var methodInfo = serviceType.GetMethod(_methodName);

            var dynamicRequestType = methodInfo.GetParameters()[0].ParameterType;

            //var dynamicRequestType = default(Type);
            //if (request != null)
            //{
            //    var requestName = request.GetType().FullName;
            //    var match = Regex.Match(requestName, @"^System\..+", RegexOptions.IgnoreCase);
            //    if (match.Success)
            //    {
            //        dynamicRequestType = Type.GetType(requestName);
            //    }
            //    else
            //    {
            //        dynamicRequestType = assembly.GetType(@namespace + "." + request.GetType().Name, true, true);
            //    }
            //}

            var asyncMethodInfo = serviceType.GetMethod(_methodName + "Async", new Type[] { dynamicRequestType });
            var eventInfo = serviceType.GetEvent(_methodName + "Completed");

            var method = default(WSMethod);

            lock (wsInfos)
            {
                var wsInfo = wsInfos.SingleOrDefault(d => d.Url == url);
                if (wsInfo == null)
                {
                    wsInfo = new WSInfo() { Url = url, Assembly = assembly, Methods = new List<WSMethod>(), Class = service };
                    wsInfos.Add(wsInfo);
                }
                method = new WSMethod() { MethodName = _methodName, Method = methodInfo, AsyncMethod = asyncMethodInfo, AsyncEvent = eventInfo, DynamicType = dynamicRequestType };
                method.Class = service;
                //复用WSInfo里面的服务实例
                if (wsInfo.Methods.Count == 0)
                {
                    wsInfo.Methods.Add(method);
                }
                else
                {
                    if (wsInfo.Methods.SingleOrDefault(m => m.MethodName == _methodName) == null)
                    {
                        wsInfo.Methods.Add(method);
                    }
                }
            }
            return method;
        }

        /// <summary>
        /// 动态调用
        /// </summary>
        /// <param name="assembly"></param>
        /// <param name="url"></param>
        /// <param name="_methodName"></param>
        /// <param name="request"></param>
        /// <param name="service"></param>
        /// <returns></returns>
        private string DynamicCalling(Assembly assembly, string url, string _methodName, string request, object service)
        {
            var method = ProcessMethod(assembly, url, _methodName, service);
            var requestBody = SerializeHelper.Deserialize(request, method.DynamicType);
            //此处重用服务类，服务必须是无状态字段的，等于是所有请求单例模式，会带有请求之间的状态
            //No，这里是代理类，不是服务类
            string responseBody = SerializeHelper.Serialize(method.Method.Invoke(method.Class, new object[] { requestBody }));
            return responseBody;
        }

        /// <summary>
        /// 根据web	service文档架构向代理类添加ServiceDescription和XmlSchema
        /// </summary>
        /// <param name="serviceDescriptionImporter"></param>
        private void CheckServiceDescriptionImporterImports(ServiceDescriptionImporter serviceDescriptionImporter)
        {
            var discoveryClientProtocol = new DiscoveryClientProtocol();
            discoveryClientProtocol.DiscoverAny(wsdlUrl);
            discoveryClientProtocol.ResolveAll();
            foreach (var osd in discoveryClientProtocol.Documents.Values)
            {
                if (osd is ServiceDescription) serviceDescriptionImporter.AddServiceDescription((ServiceDescription)osd, null, null);
                if (osd is XmlSchema) serviceDescriptionImporter.Schemas.Add((XmlSchema)osd);
            }
        }

        #endregion
    }
}
